package org.ponly.cta4j.spring;

import java.io.FileNotFoundException;
import java.net.URL;
import net.sf.ehcache.CacheManager;
import org.ponly.cta4j.service.DefaultManageableCaptchaService;
import org.ponly.cta4j.service.store.ehcache.EhcacheStore;
import org.ponly.cta4j.service.store.redis.RedisStore;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import redis.clients.jedis.JedisPool;


/**
 * 验证码 spring bean 定义解析器.
 *
 * @author vacoor
 * @since 1.0
 */
public class CaptchaServiceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    /**
     * 协议分隔符.
     */
    private static final String PROTOCOL_SEPARATOR = "://";

    /**
     * class path location prefix.
     */
    private static final String CLASSPATH_PREFIX = "classpath:";

    /**
     * all class path location prefix.
     */
    private static final String CLASSPATH_ALL_PREFIX = "classpath*:";

    /**
     * default port for redis.
     */
    private static final int REDIS_PORT = 6379;

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean shouldGenerateIdAsFallback() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Class<?> getBeanClass(final Element element) {
        return DefaultManageableCaptchaService.class;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) {
        super.doParse(element, parserContext, builder);

        // parse store.
        final String storeRef = element.getAttribute("store-ref");
        final String storeUrl = element.getAttribute("store");
        if (StringUtils.hasText(storeRef)) {
            builder.addConstructorArgReference(storeRef);
        } else if (StringUtils.hasText(storeUrl)) {
            builder.addConstructorArgValue(doParseStore(storeUrl));
        } else {
            throw new IllegalStateException("store is not configure?");
        }

        // parse factory.
        final String factoryRef = element.getAttribute("factory-ref");
        final String factory = element.getAttribute("factory");

        if (StringUtils.hasText(factoryRef)) {
            builder.addConstructorArgReference(factoryRef);
        } else if (StringUtils.hasText(factory)) {
            builder.addConstructorArgValue(factory);
        }
    }

    /**
     * 解析 store 配置.
     * <p/>
     * protocol:host[:port] <br />
     * redis://10.10.0.101:6379 <br />
     * memcached://10.10.0.101:11211 <br />
     * ehcache://location[#cache] <br />
     * ehcache://jvm:cache_manager_ref[#cache] <br />
     *
     * @param storeUrl 配置的存储设备 url
     * @return BeanDefinition
     */
    protected BeanDefinition doParseStore(final String storeUrl) {
        final int i = storeUrl.indexOf(PROTOCOL_SEPARATOR);
        final int j = storeUrl.indexOf(':', i + 3);

        if (i < 0) {
            throw new IllegalArgumentException("miss protocol for " + storeUrl + '.');
        }

        final String protocol = storeUrl.substring(0, i);
        final String address = storeUrl.substring(i + PROTOCOL_SEPARATOR.length(), -1 < j ? j : storeUrl.length());
        final String portPair = -1 < j ? storeUrl.substring(j + 1) : null;

        if ("ehcache".equals(protocol)) {
            return doParseEhcacheStore(storeUrl.substring(i + PROTOCOL_SEPARATOR.length()));
        } else if ("redis".equals(protocol)) {
            return doParseRedisStore(address, portPair);
        }
        throw new UnsupportedOperationException("Unsupported protocol: " + protocol);
    }

    /**
     * 解析 ehcache store 地址.
     * <p/>
     * ehcache://ehcache.xml[#cache] <br />
     * ehcache://jvm:cache_manager_ref[#cache] <br />
     *
     * @param address ehcache config
     * @return BeanDefinition
     */
    protected BeanDefinition doParseEhcacheStore(final String address) {
        final BeanDefinitionBuilder storeBuilder = BeanDefinitionBuilder.rootBeanDefinition(EhcacheStore.class);
        if (address.startsWith("jvm/")) {
            // ehcache://jvm/cmRef[#cache]
            final String ehcacheRef = address.substring(4);
            final int m = ehcacheRef.indexOf('#');
            if (-1 < m) {
                storeBuilder.addConstructorArgReference(ehcacheRef.substring(0, m));
                storeBuilder.addConstructorArgValue(ehcacheRef.substring(m + 1));
            } else {
                storeBuilder.addConstructorArgReference(ehcacheRef.substring(0, m));
            }
        } else {
            try {
                // ehcache://ehcache.xml[#cache]
                final BeanDefinitionBuilder cmBuilder = BeanDefinitionBuilder.rootBeanDefinition(CacheManager.class);
                final int m = address.indexOf('#');

                cmBuilder.setDestroyMethodName("shutdown");

                String cacheName = null;
                if (-1 < m) {
                    cmBuilder.addConstructorArgValue(resolve(address));
                    cacheName = address.substring(m + 1);
                } else {
                    cmBuilder.addConstructorArgValue(resolve(address));
                }

//                cmBuilder.addConstructorArgValue(resBuilder.getBeanDefinition());
                storeBuilder.addConstructorArgValue(cmBuilder.getBeanDefinition());
                if (null != cacheName) {
                    storeBuilder.addConstructorArgValue(cacheName);
                }
            } catch (final Exception ex) {
                throw new IllegalArgumentException(ex);
            }
        }
        return storeBuilder.getBeanDefinition();
    }

    /**
     * parse redis store.
     *
     * @param host    redis host
     * @param portStr redis port
     * @return redis store
     */
    protected BeanDefinition doParseRedisStore(final String host, final String portStr) {
        int port = REDIS_PORT;

        if (StringUtils.hasText(portStr)) {
            try {
                port = Integer.parseInt(portStr);
            } catch (final NumberFormatException ex) {
                // ignore
                // TODO
            }
        }

        final BeanDefinitionBuilder jedisPool = BeanDefinitionBuilder.rootBeanDefinition(JedisPool.class);
        jedisPool.addConstructorArgValue(host);
        jedisPool.addConstructorArgValue(port);

        return BeanDefinitionBuilder.rootBeanDefinition(RedisStore.class)
                .addConstructorArgValue(jedisPool.getBeanDefinition())
                .getBeanDefinition();
    }

    /**
     * 解析资源地址到 URL, 支持 classpath:, classpath*:, file: .
     *
     * @param location 资源路径
     * @return 资源的 URL
     * @throws FileNotFoundException
     */
    private URL resolve(final String location) throws FileNotFoundException {
        URL url = null;
        String loc = location;
        if (loc.startsWith(CLASSPATH_PREFIX)) {
            loc = loc.substring(CLASSPATH_PREFIX.length());
            loc = loc.startsWith("/") ? loc.substring(1) : loc;
            url = getClass().getClassLoader().getResource(loc);
        } else if (loc.startsWith(CLASSPATH_ALL_PREFIX)) {
            loc = loc.substring(CLASSPATH_ALL_PREFIX.length());
            loc = loc.startsWith("/") ? loc.substring(1) : loc;
            url = getClass().getClassLoader().getResource(loc);
        }

        if (null == url) {
            url = ResourceUtils.getURL(loc);
        }
        return url;
    }
}
